<template>
    <div class="form-generator flex-box row-between">
        <a-modal title="表单基础信息" :closable="false" visible :maskClosable="false" v-if="modal" @ok="handleOk">
            <a-form ref="infoForm" :form="infoForm">
                <a-form-item label="表单类型">
                    <a-select allowClear :options="opts.type" v-decorator="['dataFormType', { initialValue: 1, rules: [{ required: true, message: '请选择表单类型' }] }]"> </a-select>
                </a-form-item>
                <a-form-item label="表单名称">
                    <a-input allowClear v-decorator="['dataFormName', { initialValue: '测试表单1', rules: [{ required: true, message: '请输入表单名称' }] }]" />
                </a-form-item>
                <a-form-item label="表单模型">
                    <a-select allowClear :options="opts.model" v-decorator="['dataModelId', { initialValue: null, rules: [{ required: true, message: '请选择表单模型' }] }]"> </a-select>
                </a-form-item>
                <a-form-item label="父级表单">
                    <a-select allowClear :options="opts.form" v-decorator="['parentId', { initialValue: null }]"> </a-select>
                </a-form-item>
            </a-form>
            <a-button type="primary" slot="footer" @click="handleOk" :loading="loading">确定</a-button>
        </a-modal>
        <template v-show="!modal">
            <!-- 组件库 -->
            <ul class="pannel flex-box row-around wrap component-pannel">
                <li class="form-item" v-for="(item, index) in components" :key="index" draggable="true" @dragstart="handleDragstart($event, item.key)">
                    <p class="form-item__icon">
                        <a-icon :type="item.icon"></a-icon>
                    </p>
                    <p class="form-item__name">{{ item.name }}</p>
                </li>
            </ul>
            <!-- 表单容器 -->
            <a-form ref="form" class="form-container" :form.sync="formObj" @drop.native="handleDrop" @dragover.native.stop.prevent>
                <a-row :class="{ selected: r === rowIndex }" v-for="(row, r) in form.rows" :key="r" @click.native="handleToggleRow(r)" @drop.native.stop="handleDropRow($event, r)" @dragover.native.stop.prevent>
                    <a-col
                        :class="{ selected: r === rowIndex && c === colIndex }"
                        :span="col.span"
                        :offset="col.offset"
                        v-for="(col, c) in row.cols"
                        :key="r + '' + c"
                        @click.native.stop="handleToggleCol(r, c)"
                        @drop.native.stop="handleDropCol($event, r, c)"
                        @dragover.native.stop.prevent
                    >
                        <a-form-item
                            :label.sync="col.field.label"
                            :label-col="{ span: col.field.labelSpan }"
                            :wrapper-col="{ span: col.field.wrapperSpan }"
                            :class="{ hide: !col.field.show }"
                            @click.native.stop="handleToggleCol(r, c)"
                            v-show="col.field.show"
                        >
                            <form-item :ref="col.field.id" :field="col.field" :value="[]" v-if="col.field.key === 'list'"></form-item>
                            <form-item :ref="col.field.id" :field.sync="col.field" v-decorator="castRule(col.field)" v-else></form-item>
                        </a-form-item>
                    </a-col>
                </a-row>
            </a-form>
            <!-- 属性面板 -->
            <antd-pannel :col.sync="col" :row.sync="row" :col-index="colIndex" :row-index="rowIndex" :form="form" @delete-col="handleDelCol" @delete-row="handleDelRow" @save="handleSave"></antd-pannel>
        </template>
    </div>
</template>

<script>
import Sortable from 'sortablejs';
import components from './antd';
import FormItem from './FormItem';
import FormTable from './FormTable';
import AntdPannel from './AntdPannel';
import { get, all as allForm, create, update } from '@/api/form';
import { all as allModel } from '@/api/formmodel';
export default {
    name: 'FormGenerator',
    components: { FormItem, FormTable, AntdPannel },
    provide() {
        return {
            form: this,
            castRule: this.castRule
        };
    },
    props: {
        id: {
            type: String
        }
    },
    data() {
        return {
            loading: true,
            // 组件库
            components,
            // 表单结构
            form: {
                rows: [],
                fields: []
            },
            // 是否显示弹窗
            modal: true,
            // 当前拖出组件key
            dragKey: null,
            // 当前选中行
            rowIndex: -1,
            // 当前选中列
            colIndex: -1,
            // 当前拖拽列
            dragCol: null,
            // 当前拖拽列索引
            dragColIndex: -1,
            opts: {
                // 所有表单
                form: [],
                // 所有模型
                model: [],
                // 表单类型
                type: [
                    {
                        label: '系统内表',
                        value: 1
                    },
                    {
                        label: '系统外表',
                        value: 2
                    }
                ]
            },
            // 弹窗表单数据
            infoForm: this.$form.createForm(this, { name: 'infoForm' }),
            // 容器表单数据
            formObj: this.$form.createForm(this, { name: 'form' })
        };
    },
    computed: {
        // 当前选中行
        row() {
            return this.form.rows[this.rowIndex] || { cols: [] };
        },
        // 当前选中列
        col() {
            return (this.row.cols || [])[this.colIndex] || { field: { props: {}, slots: {}, events: {} } };
        }
    },
    async mounted() {
        this.loading = true;
        this.handleLoadData();
        let id = this.id || this.$route.query.id;
        if (id) {
            this.modal = false;
            this.$loading.show();
            let res = await get(id);
            this.handleLayout(res.data);
        } else {
            this.loading = false;
        }
    },
    methods: {
        // 表单提交
        async submit() {
            this.formObj.validateFieldsAndScroll(err => {
                if (!err) {
                    alert(JSON.stringify(this.formObj.getFieldsValue()));
                } else {
                    alert('校验失败');
                    console.info(this.formObj.getFieldsValue(), err);
                }
            });
        },
        async handleLoadData() {
            //   查询所有表单以供选择父级表单
            let res = await allForm();
            this.opts.form = res.data.map(item => {
                return {
                    label: item.dataFormName,
                    value: item.dataFormId
                };
            });
            // 查询所有表单模型
            res = await allModel();
            this.opts.model = res.data.map(item => {
                return {
                    label: item.dataModelName,
                    value: item.dataModelId
                };
            });
        },
        // 表单保存
        async handleSave() {
            try {
                let data = Object.assign({}, this.form);
                data.dataFormLayout = JSON.stringify(data.rows);
                let res = await update(data);
                this.$message.success('保存成功');
                this.$emit('save');
            } catch (e) {
                this.$message.error('保存失败');
                console.warn(e);
            }
        },
        // 提交表单基础信息
        handleOk() {
            const _this = this;
            this.infoForm.validateFieldsAndScroll(async err => {
                if (!err) {
                    this.$loading.show();
                    this.form.dataFormLayout = JSON.stringify(this.form.rows);
                    let res = await create(Object.assign(this.infoForm.getFieldsValue(), this.form));
                    this.handleLayout(res.data);
                }
            });
        },
        // 自动表单布局
        async handleLayout(data) {
            this.form = Object.assign(this.form, data);
            this.form.rows = [];
            let rows = JSON.parse(this.form.dataFormLayout || '[]');
            if (rows.length === 0) {
                //   自动放置默认组件
                let id = 0;
                for (let i = 0; i < data.fields.length; i += 4) {
                    let arr = data.fields.slice(i, i + 4);
                    let cols = arr.map(field => {
                        let col = { offset: 0, span: 6 };
                        col.field = this.components.find(item => item.key === 'input').clone();
                        col.field.id = [this.form.code, 'input', ++id].join('_');
                        col.field.field = field.name;
                        col.field.label = field.comment;
                        col.field.required = field.required;
                        col.field.props.placeholder = '请输入' + field.comment;
                        return col;
                    });
                    rows.push({ cols });
                }
            }
            for (let i in rows) {
                let item = rows[i];
                this.dragKey = 'row';
                await this.handleDrop();
                this.form.rows[i].cols = item.cols;
            }
            this.handleInit();
        },
        // 初始化表单容器，创建拖拽排序实例
        handleInit() {
            const _this = this;
            this.modal = false;
            this.$nextTick(() => {
                new Sortable(this.$refs.form.$el, {
                    onUpdate: function(e) {
                        if (e.oldIndex === _this.rowIndex) {
                            _this.rowIndex = e.newIndex;
                        }
                    }
                });
                this.loading = false;
                this.$loading.hide();
            });
        },
        // 计算校验规则
        castRule(field) {
            let res = [
                field.field,
                {
                    initialValue: field.value,
                    rules: [{ required: field.required, message: field.message }]
                }
            ];
            if (field.key === 'input') {
                res[1].rules.push({ pattern: new RegExp(field.reg), message: field.message });
            }
            if (field.key === 'number') {
                res[1].rules.push({ min: field.min, message: field.message });
                res[1].rules.push({ max: field.max, message: field.message });
            }
            return res;
        },
        // 左侧组件拖拽开始
        handleDragstart(e, key) {
            this.dragKey = key;
        },
        // 放置行到表单容器并创建Sortable实例
        handleDrop(e) {
            return new Promise(resolve => {
                switch (this.dragKey) {
                    case 'row':
                        this.form.rows.push({ id: this.form.rows.length, cols: [] });
                        this.$nextTick(() => {
                            const _this = this;
                            let rowIndex = this.form.rows.length - 1;
                            let callback = function() {
                                var temp = this.form.rows[rowIndex].cols.slice(0);
                                this.form.rows[rowIndex].cols = [];
                                this.$nextTick(function() {
                                    this.form.rows[rowIndex].cols = temp;
                                });
                            }.bind(this);
                            new Sortable([...this.$refs.form.$el.childNodes].pop(), {
                                group: 'form-row',
                                onUpdate: function(e) {
                                    if (e.oldIndex === _this.colIndex) {
                                        _this.colIndex = e.newIndex;
                                    }
                                },
                                onChoose(e) {
                                    _this.dragCol = _this.form.rows[rowIndex].cols[e.oldIndex];
                                    _this.dragColIndex = e.oldIndex;
                                },
                                onRemove(e) {
                                    _this.form.rows[rowIndex].cols.splice(_this.dragColIndex, 1);
                                    callback();
                                },
                                onAdd(e) {
                                    _this.form.rows[rowIndex].cols.splice(e.newIndex, 0, _this.dragCol);
                                    callback();
                                }
                            });
                            console.info('init sortable ' + rowIndex);
                            resolve();
                        });
                        break;
                    default:
                        this.$message.error('请先放一个行组件');
                        resolve();
                        break;
                }
                this.dragKey = null;
            });
        },
        // 将组件拖拽到行组件进行放置操作
        handleDropRow(e, r) {
            switch (this.dragKey) {
                case 'col':
                    this.form.rows[r].cols.push({
                        span: 6,
                        offset: 0,
                        field: this.components[4].clone(),
                        props: {}
                    });
                    this.rowIndex = r;
                    this.colIndex = this.form.rows[r].cols.length - 1;
                    break;
                default:
                    this.$message.error('请先放一个列组件');
                    break;
            }
            this.dragKey = null;
        },
        // 将组件拖拽到列组件进行放置操作
        handleDropCol(e, r, c) {
            if (this.dragKey && ['row', 'col'].indexOf(this.dragKey) === -1) {
                let count = this.form.rows.map(item => item.cols).flat().length;
                let row = this.form.rows[r],
                    col = row.cols[c];
                let label = col.field.label || '字段' + count;
                let field = col.field.field || 'field' + count;
                let obj = this.components.find(item => item.key === this.dragKey).clone();
                obj.label = label;
                obj.field = field;
                if (col.field.id) {
                    console.info(col.field.id);
                    if (/^\d+$/.test(col.field.id)) {
                        obj.id = [this.form.code, obj.key, this.form.rows.map(item => item.cols).flat().length].join('_');
                    } else {
                        obj.id = col.field.id.replace(`_${col.field.key}_`, `_${obj.key}_`);
                    }
                }
                this.dragKey = null;
                this.rowIndex = r;
                this.colIndex = c;
                this.col.field = obj;
            } else {
                this.$message.error('暂不支持嵌套行列');
            }
        },
        // 取消/选中行
        handleToggleRow(r) {
            if (this.rowIndex === r) {
                this.rowIndex = -1;
            } else {
                this.rowIndex = r;
            }
            this.colIndex = -1;
        },
        // 取消/选中列
        handleToggleCol(r, c) {
            if (this.colIndex === c) {
                this.colIndex = -1;
            } else {
                this.colIndex = c;
            }
            this.rowIndex = r;
        },
        // 删除列
        handleDelCol() {
            if (this.colIndex !== -1) {
                this.form.rows[this.rowIndex].cols.splice(this.colIndex, 1);
            }
        },
        // 删除行
        handleDelRow() {
            if (this.rowIndex !== -1) {
                this.form.rows.splice(this.rowIndex, 1);
            }
        }
    }
};
</script>

<style lang="scss" scoped>
.flex-box {
    display: flex;
    &.col {
        flex-direction: column;
    }
    &.row-between {
        justify-content: space-between;
    }
    &.row-around {
        justify-content: space-around;
    }
    &.wrap {
        flex-wrap: wrap;
    }
}
h2 {
    text-align: center;
    width: 100%;
}
.el-form {
    .a-form-item {
        width: 100%;
        display: flex;
        margin-bottom: 0;
        /deep/ .a-form-item__label {
            font-size: 18px;
        }
        /deep/ .a-form-item__content {
            margin-left: 0 !important;
            width: 100%;
            flex-shrink: 10000;
            .el-button,
            .el-input-number {
                width: 100%;
            }
            span {
                color: black;
            }
        }
        &.hide {
            opacity: 0.3;
        }
    }
}
.form-generator {
    width: 100%;
    min-height: 800px;
    .component-pannel {
        background: lightblue;
        width: 200px;
        height: calc(100% - 20px);
        list-style: none;
        padding: 10px 0;
        user-select: none;
        .form-item {
            width: 80px;
            height: 80px;
            background: rgba(0, 0, 0, 0.3);
            text-align: center;
            color: skyblue;
            margin: 5px;
            .form-item__icon {
                font-size: 48px;
                line-height: 56px;
                margin: 0;
            }
            .form-item__name {
                margin: 0;
            }
        }
    }
    .form-container {
        height: 100%;
        padding-bottom: 200px;
        flex-grow: 1;
        background: rgba(0, 0, 0, 0.1);
        .ant-row {
            width: 100%;
            &:not(.ant-form-item) {
                background: rgba(255, 255, 255, 0.3);
                padding: 5px 0;
                margin-bottom: 5px;
                min-height: 64px;
                &.selected {
                    border: 2px white dashed;
                }
                [class^='ant-col-'] {
                    background: rgba(255, 255, 255, 0.3);
                    min-height: 50px;
                    line-height: 50px;
                    font-size: 24px;
                    text-align: center;
                    display: flex;
                    align-items: center;
                    &:nth-child(2n) {
                        background: rgba(255, 255, 255, 0.5);
                    }
                    &.selected {
                        border: 2px white dashed;
                    }
                }
            }
        }
    }
}
</style>
